﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Excel = Microsoft.Office.Interop.Excel;
using System.Reflection;
using System.Threading;
using System.Diagnostics;
using System.Xml;
using System.Data.SqlClient;
using System.Drawing;
using System.Linq;
using System.Xml.Linq;
using System.Globalization;

namespace AXTestRunner
{
    class Program
    {
        static TextReader configReader = null;
        static int intResult = 0;
        static int Main(string[] args)
        {
            //Checks for zero input
            if (args.Length < 1)
            {
                Console.WriteLine("No test suite names found in arguments.  Please enter at least one valid test suite name.");
                Environment.Exit(1);
            }

            try
            {
                Console.WriteLine("Reading config file located at C:\\tmp\\config.txt...");
                configReader = new StreamReader("C:\\tmp\\config.txt");
            }
            catch (Exception e)
            {
                Console.WriteLine("The config file could not be read:");
                Console.WriteLine(e.Message);
                Environment.Exit(1);
            }

            //Location fo Dynamics AX's results log file.  This is replaced after each call to ax32.exe
            string axResultsLocation = configReader.ReadLine();

            // Run the AX test cases
            Console.WriteLine("Running Dynamics AX tests...");
            ExecuteAXTestCases(args, axResultsLocation);

            // Convert AX results XML to Objects
            Console.WriteLine("Converting Dynamics AX results XML to Objects...");
            IEnumerable<TestCase> tests = ProcessAXTestResultsXML(args);

            getReturnCode(tests);

            // Create TRX file
            Console.WriteLine("Creating TRX File...");
            generateTRXFile(tests);

            Console.WriteLine("Test(s) Complete.");
            //Returns zero if all tests pass.  Returns 1 if at least one fails.
            return intResult;
        }

        /*
         * Looks at each of the arguments and attempts to call ax32.exe to execute the tests named by the arguments.  
         * After each test is run, the AX test results are read from the default AX location.
         * The read files are copied to the local directory under the arguments name.
         * 
         * Parameters:
         * string[] args - Array of tests to be run by ax32.exe
         * string location - The location and filename that ax32.exe writes its results to.
         * 
         * Returns:
         * void
         */
        private static void ExecuteAXTestCases(string[] args, string location)
        {
            // create a writer and open the file
            TextWriter tw=null;
            TextReader tr=null;
            String temp="";
            var dict = new Dictionary<string, bool>();
            char[] delimiterChars = { ' ' };

            ProcessStartInfo startInfo = new ProcessStartInfo();

            foreach (string w in args)
            {
                if (dict.ContainsKey(w) == true)
                {
                    Console.WriteLine("A duplicate occurrence of " + w + " was detected. Running the same suite in a single execution is not allowed.  Please remove it and try again.");
                    Environment.Exit(1);
                }
                else
                {
                    dict.Add(w, true);
                }
            }

            //Execute each test case/suite/project that is supplied in the arguments.
            foreach (string w in args)
            {
                startInfo.CreateNoWindow = true;
                startInfo.FileName = "ax32.exe";
                startInfo.Arguments = "-StartupCmd=RunTestProject_" + w;
                Process process = new Process();
                process.StartInfo = startInfo;
                process.Start();
                process.WaitForExit();

                Console.WriteLine("\tRunning test: " + w);
                try
                {
                    tw = new StreamWriter(w + ".xml");
                }
                catch (Exception e)
                {
                    Console.WriteLine("The write file could not be opened: ");
                    Console.WriteLine(e.Message);
                    Environment.Exit(1);
                }

                Console.WriteLine("\tReading AX Log File for " + w + "...");
                try
                {
                    tr = new StreamReader(location);
                    temp = tr.ReadToEnd();
                    
                }
                catch (Exception e)
                {
                    Console.WriteLine("The Dynamics AX Log file for " + w + " could not be read:");
                    Console.WriteLine(e.Message);
                    Console.WriteLine("Is this a valid test suite name?");
                    Environment.Exit(1);
                }

                //Copy everything over and close the write file
                if (tw != null)
                {
                    tw.Write(temp + "\n\n");
                    tw.Close();
                }

                //Close read file.
                if (tr != null)
                {
                    tr.Close();
                    File.Delete(location);
                }
            }
        }

        /*
         *  Prepares AX test results to be placed in a TRX file.
         *  
         * Parameters:
         * string[] testNames - Used to identify which .xml files to read from to retrieve test data.
         * 
         * Returns:
         * IEnumerable<TestCase> - Test Case structure containing all relevant test case information on all ran tests, which can be easily written to a TRX file.
         * */
        private static IEnumerable<TestCase> ProcessAXTestResultsXML(string[] testNames)
        {
            XDocument doc = null;
            List<TestCase> tests = new List<TestCase>();
            foreach (string name in testNames)
            {
                string fileName = name + ".xml";
                try
                {
                    doc = XDocument.Load(fileName);
                }
                catch (FileNotFoundException)
                {
                    Console.Error.WriteLine("AX XML file does not exists at: " + fileName);
                    Environment.Exit(1);
                }

           
                tests.AddRange((from test in doc.Descendants("test-case")
                         select new TestCase()
                         {
                             Name = test.Attribute("name").Value,
                             TestRunTime = Convert.ToInt32(test.Attribute("time").Value),
                             Success = (test.Attribute("success").Value.Equals("true") ? true : false),
                             Message = test.Element("failure") == null ? "" : test.Element("failure").Element("message").Value
                         }).ToList<TestCase>());
                tests.AddRange((from test in (doc.Descendants("test-suite"))
                                select new TestCase()
                                {
                                    Name = test.Attribute("name").Value,
                                    Success = (test.Attribute("success").Value.Equals("true") ? true : false)
                                }).ToList<TestCase>());
            }
            return tests;
        }

        /*
         * Converts a Dynamics AX test result XML file to a TRX(Test Result XML) file.       
         * 
         * Parameters:
         * IEnumerable<TestCase> tests - Test Case structure containing all relevant test case information on all ran tests, which can be easily written to a TRX file.
         * 
         * Returns:
         * void
         */
        private static void generateTRXFile(IEnumerable<TestCase> tests)
        {

            StreamWriter fr = new StreamWriter("ax_results.trx");

            fr.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
            fr.WriteLine("<SummaryResult>");
            fr.WriteLine("<TestName>" + "GenericTest" + "</TestName>");
            fr.WriteLine("<TestResult>" + (intResult == 0 ? "Passed" : "Failed") + "</TestResult>");
            fr.WriteLine("<InnerTests>");
            foreach (TestCase test in tests)
            {
                fr.WriteLine("<InnerTest>");
                fr.WriteLine("<TestName>" + test.Name + "</TestName>");
                fr.WriteLine("<TestResult>" + (test.Success ? "Passed" : "Failed") + "</TestResult>");
                if (!test.Success)
                {
                    fr.WriteLine("<ErrorMessage>" + test.Message + "</ErrorMessage>");
                }
                fr.WriteLine("</InnerTest>");
            }
            fr.WriteLine("</InnerTests>");
            fr.WriteLine("</SummaryResult>");
            fr.Close();
        }


        /*
         * Checks every ran test for a failure.  If a failure is found, the global variable intResult is set to 1.
         * 
         * Parameters:
         * Ienumerable<TestCase> tests - Test Case structure containing all relevant test case information on all ran tests, which can be easily written to a TRX file.
         * 
         * Returns:
         * void
         * 
         */
        private static void getReturnCode(IEnumerable<TestCase> tests)
        {
            foreach (TestCase test in tests)
            {
                if (!test.Success)
                {
                    intResult = 1;
                    return;
                }
            }
        }
    }

    /*
     * Test Case structure containing all relevant test case information on a given test, which can be easily written to a TRX file.
     */
    public class TestCase
    {
        public string Name { get; set; }
        public int TestRunTime { get; set; }
        public bool Success { get; set; }
        public string Message { get; set; }

        public TestCase()
        {
            //Default to a passing test case
            Success = true;
        }
    }
}
